package com.autohammal.monitor.scheduler.support;

import java.io.Serializable;
import java.util.Arrays;

public class MonitorTimer implements Serializable {
	private static final long serialVersionUID = -8461062085281293861L;

	private TaskQueue queue = new TaskQueue();

	private TimerThread thread = new TimerThread(queue);

	@SuppressWarnings("unused")
	private Object threadReaper = new Object() {
		protected void finalize() throws Throwable {
			synchronized (queue) {
				thread.newTasksMayBeScheduled = false;
				queue.notify(); // In case queue is empty.
			}
		}
	};

	private static int nextSerialNumber = 0;

	private static synchronized int serialNumber() {
		return nextSerialNumber++;
	}

	public MonitorTimer() {
		this("Timer-" + serialNumber());
	}

	public MonitorTimer(boolean isDaemon) {
		this("Timer-" + serialNumber(), isDaemon);
	}

	public MonitorTimer(String name) {
		thread.setName(name);
		thread.start();
	}

	public MonitorTimer(String name, boolean isDaemon) {
		thread.setName(name);
		thread.setDaemon(isDaemon);
		thread.start();
	}

	public void schedule(MonitorTimerTask task, long time, long period) {
		if (time < 0)
			throw new IllegalArgumentException("Illegal execution time.");

		synchronized (queue) {
			if (!thread.newTasksMayBeScheduled)
				throw new IllegalStateException("Timer already cancelled.");

			synchronized (task.lock) {
				if (task.state != MonitorTimerTask.VIRGIN)
					throw new IllegalStateException(
							"Task already scheduled or cancelled");
				task.nextExecutionTime = time;
				task.period = period;
				task.state = MonitorTimerTask.SCHEDULED;
			}

			queue.add(task);
			if (queue.getMin() == task)
				queue.notify();
		}
	}

	public void cancel() {
		synchronized (queue) {
			thread.newTasksMayBeScheduled = false;
			queue.clear();
			queue.notify(); // In case queue was already empty.
		}
	}

	public int purge() {
		int result = 0;

		synchronized (queue) {
			for (int i = queue.size(); i > 0; i--) {
				if (queue.get(i).state == MonitorTimerTask.CANCELLED) {
					queue.quickRemove(i);
					result++;
				}
			}

			if (result != 0)
				queue.heapify();
		}

		return result;
	}
}

class TimerThread extends Thread {
	boolean newTasksMayBeScheduled = true;

	private TaskQueue queue;

	TimerThread(TaskQueue queue) {
		this.queue = queue;
	}

	public void run() {
		try {
			mainLoop();
		} finally {
			// Someone killed this Thread, behave as if Timer cancelled
			synchronized (queue) {
				newTasksMayBeScheduled = false;
				queue.clear(); // Eliminate obsolete references
			}
		}
	}

	private void mainLoop() {
		while (true) {
			try {
				MonitorTimerTask task;
				boolean taskFired;
				synchronized (queue) {
					// Wait for queue to become non-empty
					while (queue.isEmpty() && newTasksMayBeScheduled)
						queue.wait();
					if (queue.isEmpty())
						break; // Queue is empty and will forever remain; die

					// Queue nonempty; look at first evt and do the right thing
					long currentTime, executionTime;
					task = queue.getMin();
					synchronized (task.lock) {
						if (task.state == MonitorTimerTask.CANCELLED) {
							queue.removeMin();
							continue; // No action required, poll queue again
						}
						currentTime = System.currentTimeMillis();
						executionTime = task.nextExecutionTime;
						if (taskFired = (executionTime <= currentTime)) {
							if (task.period == 0) { // Non-repeating, remove
								queue.removeMin();
								task.state = MonitorTimerTask.EXECUTED;
							} else { // Repeating task, reschedule
								queue.rescheduleMin(task.period < 0 ? currentTime
										- task.period
										: executionTime + task.period);
							}
						}
					}
					if (!taskFired) // Task hasn't yet fired; wait
						queue.wait(executionTime - currentTime);
				}
				if (taskFired) // Task fired; run it, holding no locks
					task.run();
			} catch (InterruptedException e) {
			}
		}
	}
}

class TaskQueue {
	private MonitorTimerTask[] queue = new MonitorTimerTask[128];

	private int size = 0;

	int size() {
		return size;
	}

	void add(MonitorTimerTask task) {
		// Grow backing store if necessary
		if (size + 1 == queue.length)
			queue = Arrays.copyOf(queue, 2 * queue.length);

		queue[++size] = task;
		fixUp(size);
	}

	MonitorTimerTask getMin() {
		return queue[1];
	}

	MonitorTimerTask get(int i) {
		return queue[i];
	}

	void removeMin() {
		queue[1] = queue[size];
		queue[size--] = null; // Drop extra reference to prevent memory leak
		fixDown(1);
	}

	void quickRemove(int i) {
		assert i <= size;

		queue[i] = queue[size];
		queue[size--] = null; // Drop extra ref to prevent memory leak
	}

	void rescheduleMin(long newTime) {
		queue[1].nextExecutionTime = newTime;
		fixDown(1);
	}

	boolean isEmpty() {
		return size == 0;
	}

	void clear() {
		// Null out task references to prevent memory leak
		for (int i = 1; i <= size; i++)
			queue[i] = null;

		size = 0;
	}

	private void fixUp(int k) {
		while (k > 1) {
			int j = k >> 1;
			if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
				break;
			MonitorTimerTask tmp = queue[j];
			queue[j] = queue[k];
			queue[k] = tmp;
			k = j;
		}
	}

	private void fixDown(int k) {
		int j;
		while ((j = k << 1) <= size && j > 0) {
			if (j < size
					&& queue[j].nextExecutionTime > queue[j + 1].nextExecutionTime)
				j++; // j indexes smallest kid
			if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
				break;
			MonitorTimerTask tmp = queue[j];
			queue[j] = queue[k];
			queue[k] = tmp;
			k = j;
		}
	}

	void heapify() {
		for (int i = size / 2; i >= 1; i--)
			fixDown(i);
	}
}

